
#include <BCursor>
#include <BStyle>
#include <BKeyEvent>
#include <BMouseEvent>
#include <BSystem>
#include <BPainter>

#include <BGraphicWidget>
#include "member_BGraphicWidget.h"
#include "member_BGraphicItem.h"

using namespace BWE;

#define member					(*(member_BGraphicWidget*)_ptr)
#define member_allocate()		_ptr = new member_BGraphicWidget(this)
#define member_release()		delete (member_BGraphicWidget*)_ptr

BGraphicWidget::BGraphicWidget(BWidget* parent) : BScrollArea(parent)
{
	member_allocate();
}
BGraphicWidget::BGraphicWidget(const BString& title, BWidget* parent) : BScrollArea(title, parent)
{
	member_allocate();
}
BGraphicWidget::~BGraphicWidget()
{
	member_release();
}

void BGraphicWidget::setSelectMode(SelectMode selectMode)
{
	if (member.selectMode != selectMode)
	{
		member.selectMode = selectMode;
		this->clearSelection();
	}
}
SelectMode BGraphicWidget::selectMode() const
{
	return member.selectMode;
}

bool BGraphicWidget::addItem(BGraphicItem* item)
{
	if (!item)
		return false;
	if (member.items.contain(item))
		return true;
	member.items.append(item);
	if (item_member(item)->graphic && item_member(item)->graphic != this)
		item_member(item)->graphic->removeItem(item);
	if (item_member(item)->parent)
		item_member(item)->parent->removeChild(item);
	item_member(item)->attach(this);
	this->emit(Signal_ItemInserted, item);
	member.dirty = true;
	this->fresh();
	return true;
}
BGraphicItem* BGraphicWidget::addItem(BWidget* widget)
{
	BGraphicItem* item = new BGraphicItem(widget);
	addItem(item);
	return item;
}
BGraphicItem* BGraphicWidget::addItem(const BString& name)
{
	BGraphicItemHolder item = new BGraphicItem(name);
	if (addItem(item))
		return item;
	return 0;
}

bool BGraphicWidget::removeItem(BGraphicItem* item)
{
	if (!item)
		return false;
	if (item_member(item)->graphic != this)
		return false;
	if (member.selectedItems.remove(item))
	{
		item_member(item)->selected = false;
		this->emit(Signal_ItemSelected, item);
	}
	if (BGraphicItem* parent = item_member(item)->parent)
	{
		parent->removeChild(item);
	}
	else
	{
		BHolder<BGraphicItem> holder(item);
		item_member(item)->detach(this);
		member.items.remove(item);
		this->emit(Signal_ItemRemoved, item);
	}
	member.dirty = true;
	this->fresh();
	return true;
}
bool BGraphicWidget::removeItem(const BString& name)
{
	if (BGraphicItem* item = this->item(name))
		return removeItem(item);
	return false;
}
bool BGraphicWidget::removeItem(const BWidget* widget)
{
	if (BGraphicItem* item = this->item(widget))
		return removeItem(item);
	return false;
}

void BGraphicWidget::clearItems()
{
	if (member.items.size())
	{
		while (BGraphicItem* item = member.selectedItems.last())
		{
			member.selectedItems.remove(item);
			this->emit(Signal_ItemSelected, item);
		}
		while (BGraphicItem* item = member.items.last())
		{
			item_member(item)->detach(this);
			BHolder<BGraphicItem> holder(item);
			member.items.remove(item);
			this->emit(Signal_ItemRemoved, item);
		}
		member.visualItems.clear();
		this->fresh();
	}
}

bool BGraphicWidget::containItem(const BGraphicItem* item)
{
	if (!item)
		return false;
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* topGraphic = member.items[i];
		if (topGraphic == item)
			return true;
		if (topGraphic->contain(item))
			return true;
	}
	return false;
}
bool BGraphicWidget::containItem(const BString& name)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* topGraphic = member.items[i];
		if (topGraphic->name() == name)
			return true;
		if (topGraphic->findByName(name))
			return true;
	}
	return false;
}

int BGraphicWidget::itemIndex(const BGraphicItem* item)
{
	if (!item)
		return -1;
	return member.items.find(item);
}
int BGraphicWidget::itemIndex(const BString& name)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* topGraphic = member.items[i];
		if (topGraphic->name() == name)
			return i;
	}
	return -1;
}

int BGraphicWidget::itemCount() const
{
	return member.items.size();
}
BGraphicItem* BGraphicWidget::item(int index)
{
	return member.items(index);
}
const BGraphicItem* BGraphicWidget::item(int index) const
{
	return member.items(index);
}

BGraphicItem* BGraphicWidget::item(const BString& name)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		if (item->name() == name)
			return item;
	}
	return 0;
}
const BGraphicItem* BGraphicWidget::item(const BString& name) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BGraphicItem* item = member.items[i];
		if (item->name() == name)
			return item;
	}
	return 0;
}

BGraphicItem* BGraphicWidget::item(const BWidget* widget)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		if (item->widget() == widget)
			return item;
	}
	return 0;
}
const BGraphicItem* BGraphicWidget::item(const BWidget* widget) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BGraphicItem* item = member.items[i];
		if (item->widget() == widget)
			return item;
	}
	return 0;
}

BGraphicItem* BGraphicWidget::itemAt(const BPoint& point)
{
	BRect crect = this->centerRect();
	if (!crect.contain(point))
		return 0;
	BPoint pos = (point - this->offset()) / this->areaScale();
	for (int i = member.items.size() - 1; i >= 0; i--)
	{
		BGraphicItem* item = member.items[i];
		BGraphicItem* result = member.locateItem(pos, item);
		if (result)
			return result;
	}
	return 0;
}
const BGraphicItem* BGraphicWidget::itemAt(const BPoint& point) const
{
	BRect crect = this->centerRect();
	if (!crect.contain(point))
		return 0;
	BPoint offset = this->offset();
	for (int i = member.items.size() - 1; i >= 0; i--)
	{
		BGraphicItem* item = member.items[i];
		BGraphicItem* result = member.locateItem(point - offset, item);
		if (result)
			return result;
	}
	return 0;
}

BGraphicItem* BGraphicWidget::itemOfName(const BString& name)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		BGraphicItem* result = item->findByName(name);
		if (result)
			return result;
	}
	return 0;
}
const BGraphicItem* BGraphicWidget::itemOfName(const BString& name) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		BGraphicItem* result = item->findByName(name);
		if (result)
			return result;
	}
	return 0;
}

BGraphicItem* BGraphicWidget::itemOfAsset(const BValue& value)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		BGraphicItem* result = item->findByAsset(value);
		if (result)
			return result;
	}
	return 0;
}
const BGraphicItem* BGraphicWidget::itemOfAsset(const BValue& value) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		BGraphicItem* result = item->findByAsset(value);
		if (result)
			return result;
	}
	return 0;
}

BGraphicItem* BGraphicWidget::create(const BString& path)
{
	int end = path.find('/');
	if (end == path.size())
	{
		BGraphicItem* item = this->item(path);
		if (item == 0)
			item = this->addItem(path);
		return item;
	}
	BString name;
	path.substr(name, 0, end);
	BGraphicItem* item = this->item(name);
	if (item == 0)
	{
		item = this->addItem(name);
	}
	int pos = end + 1;
	while (item && pos < path.size())
	{
		end = path.find('/', pos);
		path.substr(name, pos, end);
		BGraphicItem* child = item->child(name);
		if (child == 0)
			child = item->addChild(name);
		item = child;
		pos = end + 1;
	}
	return item;
}

BGraphicItem* BGraphicWidget::locate(const BString& path)
{
	int end = path.find('/');
	if (end == path.size())
		return this->item(path);
	BString name;
	path.substr(name, 0, end);
	BGraphicItem* item = this->item(name);
	int pos = end + 1;
	while (item && pos < path.size())
	{
		end = path.find('/', pos);
		path.substr(name, pos, end);
		item = item->child(name);
		pos = end + 1;
	}
	return item;
}
const BGraphicItem* BGraphicWidget::locate(const BString& path) const
{
	int end = path.find('/');
	if (end == path.size())
		return this->item(path);
	BString name;
	path.substr(name, 0, end);
	const BGraphicItem* item = this->item(name);
	int pos = end + 1;
	while (item && pos < path.size())
	{
		end = path.find('/', pos);
		path.substr(name, pos, end);
		item = item->child(name);
		pos = end + 1;
	}
	return item;
}

bool BGraphicWidget::setSelection(BGraphicItem* selectedItem)
{
	bool selectionChanged = false;
	for (int i = 0; i < member.selectedItems.size(); i++)
	{
		BGraphicItem* item = member.selectedItems[i];
		if (selectedItem != item)
		{
			item_member(item)->selected = false;
			member.selectedItems.remove(i--);
			emit(Signal_ItemSelected, item);
			selectionChanged = true;
		}
	}
	if (!member.selectedItems.contain(selectedItem))
	{
		item_member(selectedItem)->selected = true;
		member.selectedItems.append(selectedItem);
		emit(Signal_ItemSelected, selectedItem);
		selectionChanged = true;
	}
	if (selectionChanged)
	{
		emit(Signal_SelectionChanged, member.selectedItems.size());
		this->fresh();
		return true;
	}
	return false;
}
bool BGraphicWidget::setSelection(const BGraphicItemArray& selection)
{
	bool selectionChanged = false;
	for (int i = 0; i < member.selectedItems.size(); i++)
	{
		BGraphicItem* item = member.selectedItems[i];
		if (!selection.contain(item))
		{
			item_member(item)->selected = false;
			member.selectedItems.remove(i--);
			emit(Signal_ItemSelected, item);
			selectionChanged = true;
		}
	}
	for (int i = 0; i < selection.size(); i++)
	{
		BGraphicItem* item = selection[i];
		if (!member.selectedItems.contain(item))
		{
			item_member(item)->selected = true;
			member.selectedItems.append(item);
			emit(Signal_ItemSelected, item);
			selectionChanged = true;
		}
	}
	member.selectedItems = selection;
	if (selectionChanged)
	{
		emit(Signal_SelectionChanged, member.selectedItems.size());
		this->fresh();
		return true;
	}
	return false;
}
const BGraphicItemArray& BGraphicWidget::selection() const
{
	return member.selectedItems;
}

bool BGraphicWidget::setItemSelected(BGraphicItem* item, bool selected)
{
	if (!item)
	{
		return false;
	}
	if (selected)
	{
		if (member.selectMode & Select_Multiple)
		{
			if (!member.selectedItems.contain(item))
			{
				member.selectedItems.append(item);
				item_member(item)->selected = true;
				emit(Signal_ItemSelected, item);
				emit(Signal_SelectionChanged, member.selectedItems.size());
				this->fresh();
				return true;
			}
		}
		else
		{
			for (int i = 0; i < member.selectedItems.size(); i++)
			{
				BGraphicItem* selectedItem = member.selectedItems[i];
				if (selectedItem != item)
				{
					member.selectedItems.remove(i--);
					item_member(selectedItem)->selected = false;
					emit(Signal_ItemSelected, selectedItem);
				}
			}
			if (!member.selectedItems.contain(item))
			{
				member.selectedItems.append(item);
				item_member(item)->selected = true;
				emit(Signal_ItemSelected, item);
			}
			emit(Signal_SelectionChanged, member.selectedItems.size());
			this->fresh();
		}
	}
	else
	{
		if (member.selectMode & Select_Multiple)
		{
			if (member.selectedItems.contain(item))
			{
				member.selectedItems.remove(item);
				item_member(item)->selected = false;
				this->emit(Signal_ItemSelected, item);
				this->emit(Signal_SelectionChanged, member.selectedItems.size());
				this->fresh();
				return true;
			}
		}
		else
		{
			for (int i = 0; i < member.selectedItems.size(); i++)
			{
				BGraphicItem* selectedItem = member.selectedItems[i];
				if (selectedItem != item)
				{
					member.selectedItems.remove(i--);
					item_member(selectedItem)->selected = false;
					this->emit(Signal_ItemSelected, selectedItem);
				}
			}
			if (member.selectedItems.remove(item))
			{
				item_member(item)->selected = false;
				this->emit(Signal_ItemSelected, item);
			}
			this->emit(Signal_SelectionChanged, member.selectedItems.size());
			this->fresh();
		}
	}
	return true;
}
bool BGraphicWidget::clearSelection()
{
	if (member.selectedItems.empty())
		return false;
	while (BGraphicItem* item = member.selectedItems.last())
	{
		member.selectedItems -= item;
		item_member(item)->selected = false;
		emit(Signal_ItemSelected, item);
	}
	emit(Signal_SelectionChanged, member.selectedItems.size());
	this->fresh();
	return true;
}

int BGraphicWidget::selectedItemCount() const
{
	return member.selectedItems.size();
}
BGraphicItem* BGraphicWidget::selectedItem(int index)
{
	return member.selectedItems(index);
}
const BGraphicItem* BGraphicWidget::selectedItem(int index) const
{
	return member.selectedItems(index);
}

bool BGraphicWidget::itemVisual(BGraphicItem* item) const
{
	return member.visualItems.contain(item);
}
bool BGraphicWidget::scrollToItem(BGraphicItem* item, Align align)
{
	if (!item || item_member(item)->graphic != this)
		return false;
	if (member.dirty)
	{
		member.dirty = false;
		member.freshContents();
	}
	BRect crect = this->centerRect();
	BRect area = this->area();
	if (item)
	{
		BRect rect0 = item_member(item)->rect + area.pos();
		BRect rect1 = crect.align(align, rect0.size());
		BPoint offset = rect1.pos() - rect0.pos();
		area.move(offset);
	}
	else
	{
		area = crect.align(align, area.size());
	}
	this->setArea(area);
	return true;
}

const BRectf BGraphicWidget::contentRect() const
{
	return member.contentRect;
}

bool BGraphicWidget::query(const BString& text, BValue& value) const
{
	if (text == "visual-items")
	{
		BGraphicItemArray& items = value.fill<BGraphicItemArray>();
		items = member.visualItems;
		return true;
	}
	if (text == "hovered-item")
	{
		value = member.hoveredItem;
		return true;
	}
	if (text == "content-rect")
	{
		value = member.contentRect;
		return true;
	}
	return BScrollArea::query(text, value);
}

void BGraphicWidget::scrolling(Orientation orientation)
{
	member.visualItems.reset();
	const BRect& area = this->area();
	const BRect& crect = this->centerRect();
	BPointf offset = this->offset();
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		item_member(item)->adjustRect(offset);
		if (!item_member(item)->visible)
			continue;
		member.collectVisualItems(item, area.pos(), crect);
	}
	BScrollArea::scrolling(orientation);
}

void BGraphicWidget::event(const BEvent& event)
{
	Event type = event.type();
	BGraphicItem* item = member.hoveredItem;
	if (item && type >= Event_MousePress && type <= Event_DoubleClick)
	{
		const BMouseEvent& mouseEvent = (const BMouseEvent&)event;
		BPoint offset = this->offset();
		member.mouseEvent = mouseEvent;
		member.mouseEvent.setGlobalPos(mouseEvent.pos());
		member.mouseEvent.setAbsolutePos(mouseEvent.pos() - item->absolutePos());
		member.mouseEvent.setPos(mouseEvent.pos() - item->absolutePos() - offset);
		item_member(item)->event(member.mouseEvent);
	}
	BScrollArea::event(event);
}

void BGraphicWidget::resizeEvent(const BEvent& event)
{
	BScrollArea::resizeEvent(event);
	member.dirty = true;
	this->fresh();
}

void BGraphicWidget::styleEvent(const BEvent& event)
{
	BScrollArea::styleEvent(event);
	member.styleEvent = event;
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		item_member(item)->dispenseStyles(member.styleEvent);
	}
	member.dirty = true;
}
void BGraphicWidget::freshEvent(const BEvent& event)
{
	BScrollArea::freshEvent(event);
	if (member.dirty)
	{
		member.dirty = false;
		for (int i = 0; i < member.items.size(); i++)
		{
			BGraphicItem* item = member.items[i];
			member.freshItem(item, event);
		}
		member.freshContents();
		scrolling(Orientation_Horizontal);
		this->emit(Signal_Flushed);
	}
}
void BGraphicWidget::paintEvent(const BEvent& event)
{
	BScrollArea::paintEvent(event);

	BPainter painter(this);

	const BRect& crect = this->centerRect();
	BPoint offset = this->offset();
	painter.setClip(crect);
	painter.setStyle(member.itemStyle);
	painter.pushMatrix();
	painter.translate(offset);
	painter.scale(this->areaScale());
	for (int i = 0; i < member.items.size(); i++)
	{
		BGraphicItem* item = member.items[i];
		member.paintItem(item, painter);
	}
	painter.popMatrix();
	painter.setClip(this->size());
	painter.setStyle(0);

}

void BGraphicWidget::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (BGraphicItem* item = member.hoveredItem)
	{
		item_member(item)->hovered = false;
		member.hoveredItem = 0;
		this->fresh();
	}
	BScrollArea::mouseLeaveEvent(mouseEvent);
}
void BGraphicWidget::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (BGraphicItem* item = member.hoveredItem)
	{
		item_member(item)->hovered = false;
		member.hoveredItem = 0;
		this->fresh();
	}
	BScrollArea::mouseLeaveEvent(mouseEvent);
}

void BGraphicWidget::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = true;
		this->fresh();
	}
	BScrollArea::mousePressEvent(mouseEvent);
}
void BGraphicWidget::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = false;
		this->fresh();
	}
	BScrollArea::mouseReleaseEvent(mouseEvent);
}
void BGraphicWidget::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	BPoint offset = this->offset();
	if (mouseEvent.button() == Button_None)
	{
		BGraphicItem* hoveredItem = this->itemAt(pos);
		if (member.hoveredItem != hoveredItem)
		{
			if (BGraphicItem* item = member.hoveredItem)
			{
				item_member(item)->hovered = false;
				member.mouseEvent = mouseEvent;
				member.mouseEvent.reset(Event_MouseLeave);
				member.mouseEvent.setGlobalPos(mouseEvent.pos());
				member.mouseEvent.setAbsolutePos(mouseEvent.pos() - item->absolutePos());
				member.mouseEvent.setPos(mouseEvent.pos() - item->absolutePos() - offset);
				item_member(item)->event(member.mouseEvent);
			}
			member.hoveredItem = hoveredItem;
			if (BGraphicItem* item = member.hoveredItem)
			{
				item_member(item)->hovered = true;
				member.mouseEvent = mouseEvent;
				member.mouseEvent.reset(Event_MouseEnter);
				member.mouseEvent.setGlobalPos(mouseEvent.pos());
				member.mouseEvent.setAbsolutePos(mouseEvent.pos() - item->absolutePos());
				member.mouseEvent.setPos(mouseEvent.pos() - item->absolutePos() - offset);
				item_member(item)->event(member.mouseEvent);
			}
			emit(Signal_ItemHovered, hoveredItem);
			this->fresh();
		}
	}
	BScrollArea::mouseMoveEvent(mouseEvent);
}
void BGraphicWidget::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	if (BSystem::KeyState(Key_Ctrl))
	{
		int wheel = mouseEvent.wheel();
		BPointf scale = this->areaScale();
		if (wheel > 0)
			this->setAreaScale(scale * 1.1f);
		else
			this->setAreaScale(scale * 0.9f);
		mouseEvent.accept();
	}
	BScrollArea::mouseWheelEvent(mouseEvent);
}

void BGraphicWidget::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.selectMode & Select_Single)
		{
			BGraphicItem* item = member.hoveredItem;
			if (item)
			{
				bool selectionChanged = false;
				for (int i = 0; i < member.selectedItems.size(); i++)
				{
					if (member.selectedItems[i] != item)
					{
						BGraphicItem* temp = member.selectedItems[i];
						item_member(temp)->selected = false;
						member.selectedItems.remove(i--);
						emit(Signal_ItemSelected, temp);
						selectionChanged = true;
					}
				}
				if (member.selectedItems.empty())
				{
					member.selectedItems.append(item);
					item_member(item)->selected = true;
					emit(Signal_ItemSelected, item);
					selectionChanged = true;
				}
				if (selectionChanged)
				{
					emit(Signal_SelectionChanged, member.selectedItems.size());
					this->fresh();
				}
			}
			else
			{
				clearSelection();
			}
		}
		if (member.selectMode & Select_Multiple)
		{
			BGraphicItem* item = member.hoveredItem;
			if (item == 0)
			{
				clearSelection();
			}
			else if (mouseEvent.keyState(Key_Ctrl))
			{
				bool selected = !item_member(item)->selected;
				setItemSelected(item, selected);
			}
			else
			{
				bool selectionChanged = false;
				for (int i = 0; i < member.selectedItems.size(); i++)
				{
					if (member.selectedItems[i] != item)
					{
						BGraphicItem* temp = member.selectedItems[i];
						member.selectedItems.remove(i--);
						item_member(temp)->selected = false;
						emit(Signal_ItemSelected, temp);
						selectionChanged = true;
					}
				}
				if (item && member.selectedItems.empty())
				{
					member.selectedItems.append(item);
					item_member(item)->selected = true;
					emit(Signal_ItemSelected, item);
					selectionChanged = true;
				}
				if (selectionChanged)
				{
					emit(Signal_SelectionChanged, member.selectedItems.size());
					this->fresh();
				}
			}
		}
	}
	if (mouseEvent.button() == Button_Left)
	{
		emit(Signal_ItemClicked, member.hoveredItem);
	}
	if (mouseEvent.button() == Button_Right)
	{
		emit(Signal_ItemRightClicked, member.hoveredItem);
	}
	BScrollArea::mouseClickEvent(mouseEvent);
}
void BGraphicWidget::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.hoveredItem)
		{
			emit(Signal_ItemDoubleClicked, member.hoveredItem);
		}
	}
	BScrollArea::mouseDoubleClickEvent(mouseEvent);
}
